有時候在螢幕旋轉時或是 App 修改配置的時候, Activity 的生命週期會從 onDestroy -> onCreate ,這時顯示在 UI 上的一些資料就會因為 Activity 被重新建立而消失。
由於初始化了 Activity ,包括 Activity 中的 ViewModel 等也一併被重新建立,因為新的 ViewModel 沒有資料,理所當然的 UI 上也沒有任何資料顯示了。
另一個問題是有時候 View 層需要需要執行一些非同步的呼叫,並且必須確保在 App 配置改變時被系統註銷,同時又要確保 Activity 等被重新建立時這些 Callback 能夠恢復,這會需要花費很大的心力維護。
Google 為此推出了一個好用的工具:ViewModel。
由於有著 Lifecycle-Aware Components 的幫助, ViewModel 可以在 App配置改變時自動保存 instance ,以便其資料可以在接下來的 Activity 或是 Fragment 使用。
要使用 ViewModel 首先必須在 project gradle 加入以下指令:
allprojects {
repositories {
google()
jcenter()
}
}
然後再 app gradle 加入以下指令:
dependencies {
def lifecycle_version = "2.1.0"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
}
接著修改 TasksViewModel 及 TasksActivity :
class TasksViewModel : ViewModel() {
......
}
class TasksActivity : AppCompatActivity() {
......
private lateinit var viewModel: TaskViewModel
......
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProviders.of(this).get(TaskViewModel::class.java)
......
}
......
}
這樣,TasksViewModel 的生命週期就會限制在 TasksActivity 的生命週期內,接下來再看看 ViewModel 的生命週期:
可以看到 ViewModel 在 Activity 第一次時建立也一起建立,在螢幕旋轉時依舊存在,直到 Activity finish()
並且 destroy 時才一起消滅。
ViewModel 也提供一個 AndroidViewModel
給開發者用於需要在 ViewModel 內使用 context 的情境, AndroidViewModel
的 constructor 必須注入 application context 。
有時候我們會需要往 ViewModel 內注入 entity ,例如前文中示範的 ViewModel 在 constructor 內注入 TaskRepository ,這時使用 ViewModelProviders.of(this)
是無法辦到的,那麼我們就必須要自己實現創建 ViewModel 的工廠類 ViewModelProvider.Factory
。
首先要在 TasksViewModel 加入 constructor:
class TasksViewModel(private val repository: TasksRepository) : ViewModel() {
......
}
接著建立 TodoViewModelFactory
:
@Suppress("UNCHECKED_CAST")
class TodoViewModelFactory constructor(
private val tasksRepository: TasksRepository
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>) =
with(modelClass) {
when {
isAssignableFrom(TasksViewModel::class.java) ->
TasksViewModel(tasksRepository)
else ->
throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
}
} as T
}
在 Factory 類的 constructor 注入 TasksRepository ,並且 override create
方法返回需要的 ViewModel。
最後修改在 Activity 內的調用方法:
class TasksActivity : AppCompatActivity() {
private val repository by lazy { TasksRepository() }
private val viewModelFactory by lazy { TodoViewModelFactory(repository) }
private lateinit var viewModel: TaskViewModel
......
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProviders
.of(this, viewModelFactory)
.get(TaskViewModel::class.java)
......
}
......
}
如此就完成簡單的 ViewModelFactory ,當然在實務上對 Factory 還有一些封裝,這些等到後面幾天再一一介紹。